/*
 * JUL - The JavaScript UI Language module
 * Copyright (c) 2012 - 2014 The Zonebuilder (zone.builder@gmx.com)
 * http://jul-javascript.googlecode.com/
 * @license	MIT (see attached license.txt)
 */
/**
 * @fileOverview
 * The JavaScript UI Language (JUL) is a configuration and instantiation module for the JavaScript frameworks. 
 * It can be used with any framework that accepts a configuration object as a constructor parameter, 
 * or with a custom factory for the other frameworks.<br />
 * JUL parses a tree of configuration objects, and creates the runtime components in the expected order and membership. 
 * For the most uses, after this step, you will have your application's user interface up and running.<br>
 * <br>An example of the JUL tree for a generic 'FWK.Widgets' component library:
 * <pre><code>var oUiConfig = {
    xclass: 'FWK.Widgets.DataView',
    id: 'APP.mainView',
    autoDraw: false,
    overflow: 'hidden',
    children: [{
        xclass: 'FWK.Widgets.VLayout',
        id:'VLayout0',
        children: [{
            xclass: 'FWK.Widgets.ToolStrip',
            id: 'ToolStrip0',
            visibilityMode: 'multiple'
        }, {
            xclass: 'FWK.Widgets.HLayout',
            id: 'HLayout0',
            children: [{
                xclass: 'FWK.Widgets.VLayout',
                id: 'Navigation',
                width: 200,
                showResizeBar: true
                }, {
                xclass: 'FWK.Widgets.TabSet',
                   id: 'TabSet1',
                tabs: [{
                    xclass: 'FWK.Widgets.Tab',
                    title: 'Tab1',
                    id: 'Tab1'
                }, {
                    xclass: 'FWK.Widgets.Tab',
                    title: 'Tab2',
                    id: 'Tab2'
                }],
                showTabScroller:true,
                showTabPicker:true,
                destroyPanes:false,
                showEdges:false
            }]
        }]
    }],
    width: '100%',
    height: '100%'
};
var oParser = new JUL.UI.Parser({
    classProperty: 'xclass',
    childrenProperty: 'children',
    membersProperties: ['tabs'],
    idProperty: 'id'
});
oParser.create(oUiConfig);
APP.mainView.render();</code></pre>
 * <br>Another example of the JUL tree for a XUL dialog:
 * <pre><code>var oUiConfig = {
    tag: 'dialog',
    id: 'dialog-browse',
    title: 'Open',
    width: 500,
    height: 250,
    hidden: true,
    children: [
        {tag: 'listbox', id: 'listbox-browse', selType: 'single', 
            flex: 1, children: [
            {tag: 'listhead', children: [
                {tag: 'listheader', label: 'Name', width: 300}
        ]},
            {tag: 'listbody', children: [
                {tag: 'listitem', children: [
                    {tag: 'listcell', label: 'File 1'}
                ]},
                {tag: 'listitem', children: [
                    {tag: 'listcell', label: 'File 2'}
                ]}
            ]}
        ]}
    ],
};
var oParser = new JUL.UI.Parser({
    defaultClass: 'xul',
    useTags: true,
    tagProperty: 'tag',
    customFactory: 'JUL.UI.createDom',
    topDown: true
});
var oDialog = oParser.create(oUiConfig);
oDialog.show();</code></pre><br>
 * @name jul.js
 * @author <a href="mailto:zone.builder@gmx.com">The Zonebuilder</a>
 * @version 1.0.5.2
 */
/*extern ActiveXObject, DOMParser, XMLSerializer, JSON, ample*/

// add 'indexOf' Array method, if not present
if (!Array.prototype.indexOf) {
	Array.prototype.indexOf = function(oSearch) {
		for (var i = 0; i < this.length; i++) {
			if (this[i] === oSearch) { return i; }
		}
		return -1;
	};
}

// add 'map' Array method, if not present
if (!Array.prototype.map) {
	Array.prototype.map = function(fMap, oScope) {
		if (typeof fMap !== 'function') { return null; }
		var aResult = [];
		for (var i = 0; i < this.length; i++) {
			aResult.push(fMap.call(oScope, this[i], i, this));
		}
		return aResult;
	};
}

/**
 * JUL global namespace
 * @namespace
 * It holds the utility methods used by JUL 
 */
JUL = {

	/**
	 * JUL version
	 * @type	String	 
	 */
	version: '1.0.5.2',
	
	/**
	 * Creates the specified namespace, and optionally initializes it
	 * @param {String} sPath	The dotted path for the namespace
	 * @param {Object} [oInit]	An optional initializer
	 * @returns {Object}	The created or existing namespace
	 */
	ns: function(sPath, oInit) {
		var aNames = sPath.split('.');
		var sItem = '';
		var oCurrent = window;
		if ('window' === aNames[0]) { aNames.shift(); }
		while (aNames.length) {
			sItem = aNames.shift();
			if (oCurrent[sItem] === undefined) {
				if (!aNames.length && oInit) {
					oCurrent[sItem] = oInit;
				}
				else {
					oCurrent[sItem] = {};
				}
			}
			oCurrent = oCurrent[sItem];
		}
		return oCurrent;
	},

	/**
	 * Retrieves a member specified by a namespace path
	 * @param	{String|Object}	sPath	The dotted path or object reference to retrieve
	 * @param	{String|Object}	[oRoot]	An optional object where to start the search from
	 * @returns {Mixed}	The requested member or undefined if not found
	 */
	get: function (sPath, oRoot) {
		if (typeof sPath !== 'string') { return sPath; }
		var aNames = sPath.split('.');
		var oCurrent = oRoot || window;
		var sItem = '';
		while (aNames.length) {
			sItem = aNames.shift();
			if (oCurrent[sItem] === undefined) { return undefined; }
			oCurrent = oCurrent[sItem];
		}
		return oCurrent;
	},

	/**
	 * Applies an object or an array of objects to a given object
	 * @param	{Object}		oSource			The source object to apply to
	 * @param	{Object|Object[]}	oAdd		An Object or array of Objects to apply
	 * @param	{Boolean}		bDontReplace	Set it to true to don't replace existing members
	 * @returns	{Object}	The source object with the applied members
	 */	 
	apply: function(oSource, oAdd, bDontReplace) {
		if (!oAdd || typeof oAdd !== 'object') { return oSource; }
		var aMembers = [].concat(oAdd);
		for (var i = 0; i < aMembers.length; i++) {
			oAdd = aMembers[i];
			for (var sItem in oAdd) {
				if (oAdd.hasOwnProperty(sItem) && (!bDontReplace || oSource[sItem] !== undefined)) {
					oSource[sItem] = oAdd[sItem];
				}
			}
		}
		return oSource;
	},
	
	/**
	 * Returns the name of the object's native constructor
	 * @param	{Object}	oData	An object to get the native constructor name from
	 * @returns	{String}	The native constructor name
	 */
	typeOf: function(oData) {
		return ({}).toString.call(oData).match(/\w+/g)[1];
	},
	
	/**
	 * Trims white spaces around a string
	 * @param	{String}	sText	The string to trim
	 * @returns	{String}	Trimmed string
	 */	
	trim: function(sText) {
		return sText.replace(/\s+$/, '').replace(/^\s+/, '');
	}
};

/**
 * Creates a writable reference of a property of the given object
 * @class
 * A class for working with references in JavaScript
 * 
 * @constructor
 * @param {Object}	oRef	The object to reference or a config object with 'ref' and 'key' properties
 * @param {String}	[sKey]	The name of the property to reference
 */ 
JUL.Ref = function(oRef, sKey) {
	if (!(this instanceof JUL.Ref)) {
		return new JUL.Ref(oRef, sKey);
	}
	if (oRef instanceof JUL.Ref) {
		JUL.apply(this, oRef);
		return;
	}
	this._ref = oRef;	
	this._key = sKey;
	if (typeof oRef === 'object' && oRef.ref) {
		this._ref = oRef.ref;
		this._key = oRef.key;
		for (var sMember in oRef) {
			if (oRef.hasOwnProperty(sMember) && sMember !== 'key' && sMember !== 'ref') {
				this[sMember] = oRef[sMember];
			}
		}
	}
};

JUL.apply(JUL.Ref.prototype, {
	/**
	 * Gets or sets the referenced object or the name of the referenced property
	 * @param	{Object}	[oRef]	The new object to reference. Omit to get the current object, set to true to get the property name
	 * @param	{String}	[sKey]		The new name of the property to reference
	 * @returns	{Object|String}	The referenced object or property name if getting, nothing if setting	 	
	 */	 
	ref: function(oRef, sKey) {
		if (!oRef) { return this._ref; }
		if (oRef === true) { return this._key; }
		if (oRef) { this._ref = oRef; }
		if (sKey !== undefined) { this._key = sKey; }
		return this;
	},

	/**
	 * Gets or sets the value of the referenced property
	 * @param	{Mixed}	[oVal]	The new value to set the property to. Omit to get the current value
	 * @returns	{Mixed}	The value of the referenced property
	 */
	val: function(oVal) {
		if (oVal !== undefined) {
			this._ref[this._key] = oVal;
			return this;
		}
		return this._ref[this._key];
	},

	/**
	 * Gets or sets the name of the referenced property
	 * @param	{String|Integer}	[sKey]	The new name or index to set the property to. Omit to get the current one
	 * @returns	{String|Integer}	The name of the referenced property
	 */
	key: function(sKey) {
		if (sKey !== undefined) {
			this._key = sKey;
			return this;
		}
		return this._key;
	},
	
	/**
	 * Applies JavaScript 'delete' operator on the referenced object member
	 */
	del: function() {
		delete this._ref[this._key];
		return this;
	}
});

/**
 *  JUL.UI namespace
 * @namespace
 * JUL tools for working with a config tree of component configs<br>
 * Can be also used as an instantiator (see {@link JUL.UI.Parser} method)
 */
JUL.UI = {
	/**
	 * The name of the class property in the config tree
	 * @type	String
	 */
	classProperty: 'xclass',
	
	/**
	 * The name of the default class in the config tree
	 * @type	String	 
	 */
	defaultClass: 'Object',
	
	/**
	 * The name of the children property in the config tree
	 * @type	String	 
	 */
	childrenProperty: 'children',
	
	/**
	 * An array of names of the other 'members' properties in the config tree
	 * @type	String[]
	 */
	membersProperties: [],
	
	/**
	 * An optional custom function to instantiate a component
	 * @type	Function	 
	 */
	customFactory: null,
	
	/**
	 * Set this to true to have a top-down instantiation instead of the default bottom-up one
	 * @type	Boolean	 
	 */
	topDown: false,
	
	/**
	 * The name of the parent property in the config tree. Used if topDown is true
	 * @type	String	 
	 */
	parentProperty: 'parent',
	
	/**
	 * The name of the ID property in the config tree
	 * @type	String	 
	 */
	idProperty: 'id',
	
	/**
	 * The name of the binding property in the config tree
	 * @type	String	 
	 */
	bindingProperty: 'cid',
	
	/**
	 * Whether to use tag property in the config tree
	 * @type	Boolean	 
	 */
	useTags: false,
	
	/**
	 * The name of the tag property in the config tree
	 * @type	String	 
	 */
	tagProperty: 'tag',
	
	/**
	 * The name of the inner HTML property in the config tree
	 * @type	String	 
	 */
	htmlProperty: 'html',
	
	/**
	 * The name of the CSS class property in the config tree
	 * @type	String	 
	 */
	cssProperty: 'css',
	
	/**
	 * The name of the include property in the config tree
	 * @type	String	 
	 */
	includeProperty: 'include',
	
	/**
	 * The name pf the parser config property used a meta information for a branch of the config tree
	 * @type	String
	 */
	parserProperty: 'parserConfig',
	
	/**
	 * Hash containing XML namespaces for several DOM languages
	 * @type	Object	 
	 */
	xmlNS: {
		aml: 'http://www.amplesdk.com/ns/aml',
		aui: 'http://www.amplesdk.com/ns/aui',
		chart: 'http://www.amplesdk.com/ns/chart',
		html: 'http://www.w3.org/1999/xhtml',
		svg: 'http://www.w3.org/2000/svg',
		xform: 'http://www.w3.org/2002/xforms',
		xul: 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul'
	},
	
	/**
	 * Default factory method that creates an object of a certain class
	 * @param	{String}	sClass	Dotted path of the class constructor i.e. class full name
	 * @param	{Object}	oArgs	Constructor arguments as a config object (excluding class name)
	 * @returns	{Object}	The new created object
	 */
	factory: function (sClass, oArgs) {
		var aNames = sClass.split('.');
		var oCurrent = window;
		var sItem = '';
		while (aNames.length) {
			sItem = aNames.shift();
			if (oCurrent[sItem] === undefined) { return null; }
			if (aNames.length) { oCurrent = oCurrent[sItem]; }
		}
		if (typeof oCurrent[sItem] !== 'function') { return null; }
		if (oArgs) {
			return new oCurrent[sItem](oArgs);
		}
		else {
			return new oCurrent[sItem]();
		}
	},
	
	/**
	 * Does explicit object inheritance based on a predefined object property
	 * @param	{Object}	oData	Config object containing an include property i.e. a dotted path to another object
	 * @returns	{Object}	Object with recursively applied inherited properties where not already present
	 */
	include: function(oData) {
		var oNew = {};
		if (!oData[this.includeProperty]) {
			return JUL.apply(oNew, oData);
		}
		var aIncludes = [].concat(oData[this.includeProperty]);
		for (var i = 0; i < aIncludes.length; i++) {
			var oInclude = JUL.get(aIncludes[i]);
			if (oInclude) { JUL.apply(oNew, this.include(oInclude)); }
		}
		var aCid = oNew[this.bindingProperty] ? [].concat(oNew[this.bindingProperty]) : [];
		if (oData[this.bindingProperty] && aCid.indexOf(oData[this.bindingProperty]) < 0) {
			aCid.push(oData[this.bindingProperty]);
		}
		JUL.apply(oNew, oData);
		if (aCid.length) { oNew[this.bindingProperty] = aCid; }
		delete oNew[this.includeProperty];
		return oNew;
	},
		
		/**
		 * Creates a tree of runtime objects specified by a config tree<br>
		 * Object instances are created bottom-up by default with children instances put in the parent config, 
		 * or top-down with parent instance put in the children configs
		 * @param	{Object|Object[]}	oTree		Config tree root or array of root configs
		 * @param	{Object}			[oBindings]	Config tree logic that will apply to the corresponding nodes
		 * @param	{Object}			[oParent]	Optional parent object of the root instance when instantiating top-down
		 * @returns	{Object|Object[]}	Tree root instance or array of root instances
		 */
		create: function(oTree, oBindings, oParent) {
			if (!oTree) { return null; }
			if (oBindings && oBindings[this.includeProperty]) {
					oBindings = this.include(oBindings);
			}
			if (JUL.typeOf(oTree) === 'Array') {
				return oTree.map(function(oItem) {
					return this.create(oItem, oBindings, oParent);
				}, this);
			}
			var oRoot = {root: oTree};
			var oRootParent = {root: oParent};
			var aNodes = [new JUL.Ref({ref: oRoot, key: 'root', parent: oParent ? new JUL.Ref(oRootParent, 'root') : null})];
			var aStack = aNodes;
			while (aNodes.length) {
				var aNextNodes = [];
			for (var i = 0; i < aNodes.length; i++) {
				var oCurrent = aNodes[i];
				if (!oCurrent.val() || oCurrent.val()[this.parserProperty]) { continue; }
				oCurrent.config = oCurrent.val();
				delete oCurrent.config[this._instanceProperty];
				var oNew = this.include(oCurrent.config);
				if (oNew[this.bindingProperty]) {
					if (oBindings) {
						var aCid = [].concat(oNew[this.bindingProperty]);
						for (var k = 0; k < aCid.length; k++) {
							if (oBindings[aCid[k]]) { JUL.apply(oNew, oBindings[aCid[k]]); }
						}
					}
					delete oNew[this.bindingProperty];
				}
				if (oNew[this.idProperty] && oBindings && oBindings[oNew[this.idProperty]]) {
					JUL.apply(oNew, oBindings[oNew[this.idProperty]]);
				}
				oCurrent.val(oNew);
				for (var sItem in oNew) {
					if (oNew.hasOwnProperty(sItem) && typeof oNew[sItem] === 'object' &&
						[].concat(this.childrenProperty, this.membersProperties).indexOf(sItem) > -1) {
						var aMembers = [].concat(oNew[sItem]);
						var sType = JUL.typeOf(oNew[sItem]);
						if (sType === 'Array' || this.topDown) {
							for (var n = 0; n < aMembers.length; n++) {
									aNextNodes.push(new JUL.Ref({ref: aMembers, key: n, parent: oCurrent}));
							}
						}
						else {
								aNextNodes.push(new JUL.Ref({ref: oNew, key: sItem, parent: oCurrent}));
						}
						if (this.topDown) {
							delete oNew[sItem];
						}
						else {
							if (sType === 'Array') { oNew[sItem] = aMembers; }
						}
					}
				}
			}
			aStack = this.topDown ? aStack.concat(aNextNodes) : aNextNodes.concat(aStack);
			aNodes = aNextNodes;
		}
		for (i = 0; i < aStack.length; i++) {
			oCurrent = aStack[i];
			if (!oCurrent.val()) { continue; }
			if (oCurrent.val()[this.parserProperty]) {
				var oBranchConfig = oCurrent.val();
				var oParserConfig = oBranchConfig[this.parserProperty];
				var oBranchParser = new this.Parser(oParserConfig);
				delete oBranchConfig[this.parserProperty];
				oCurrent.val(oBranchParser.create(oBranchConfig, oBindings, this.topDown && oCurrent.parent ? oCurrent.parent.val() : null));
				oBranchConfig[this.parserProperty] = oParserConfig;
				continue;
			}
			if (this.topDown) {
				oCurrent.val()[this.parentProperty] = oCurrent.parent ? oCurrent.parent.val() : null;
			}
			oCurrent.val(this.createComponent(oCurrent.val()));
			if (this._keepInstance && oCurrent.config) {
				oCurrent.config[this._instanceProperty] = oCurrent.val();
			}
		}
		return oRoot.root;
	},
	
	/**
	 * Creates a single component given its config<br>
	 *  If the ID property is a dotted path, the component is published (made available) under that path
	 * @param	{Object}	Component config
	 * @returns	{Object}	Component instance
	 */
	createComponent: function(oConfig) {
		if (!oConfig[this.classProperty]) { oConfig[this.classProperty] = this.defaultClass; }
		var sNamespace = '';
		if (oConfig[this.idProperty]) {
			sNamespace = oConfig[this.idProperty];
			oConfig[this.idProperty] = oConfig[this.idProperty].replace('.', '-');
			if ('window.' === sNamespace.substr(0, 7)) { oConfig[this.idProperty] = oConfig[this.idProperty].substr(7); }
		}
		var sClass = oConfig[this.classProperty];
		if (!this.customFactory) { delete oConfig[this.classProperty]; }
		var oNew = this.customFactory ? JUL.get(this.customFactory).call(this, oConfig) : this.factory(sClass, oConfig);
		if (sNamespace.indexOf('.') > -1) {
			return JUL.ns(sNamespace.replace('-', '_'), oNew);
		}
		else {
			return oNew;
		}
	},
		
	/**
	 * Custom factory for DOM languages<br>
	 * To use it, set the 'customFactory' property of the parser to JUL.UI.createDom
	 * @param	{Object}	oConfig	Config object
	 * @returns	{Object}	Component instance
	 */
	createDom: function(oConfig) {
		if (!oConfig) { return null; }
		var iNS = oConfig[this.classProperty].indexOf(':');
		var sNS = iNS > -1 ? oConfig[this.classProperty].substr(0, iNS) : oConfig[this.classProperty];
		if (!this.xmlNS[sNS]) { sNS = 'html'; }
		var oDocument = document;
		var bAmple = typeof window.ample === 'object';
		if (bAmple) { oDocument = ample; }
		var oWidget = sNS === 'html' ? oDocument.createElement(iNS > -1 ? oConfig[this.classProperty].substr(iNS + 1) : oConfig[this.tagProperty]) :
			oDocument.createElementNS(this.xmlNS[sNS], iNS > -1 ? oConfig[this.classProperty] : sNS + ':' + oConfig[this.tagProperty]);
		if (!oWidget) { return null; }
		if (oConfig.listeners && typeof oConfig.listeners === 'object') {
			var oListeners = oConfig.listeners;
			for (var sItem in oListeners) {
				if (oListeners.hasOwnProperty(sItem)) {
					var aAll = [].concat(oListeners[sItem]);
					for (var j = 0; j < aAll.length; j++) {
						if (bAmple || oWidget.addEventListener) { oWidget.addEventListener(sItem, JUL.get(aAll[j])); }
						else { oWidget.attachEvent('on' + sItem, JUL.get(aAll[j])); }
					}
				}
			}
		
		}
		for (sItem in oConfig) {
			if (oConfig.hasOwnProperty(sItem) && [].concat(this.childrenProperty, this.membersProperties).indexOf(sItem) < 0 &&
				['listeners', this.cssProperty, 'style', this.htmlProperty, this.tagProperty, this.classProperty, this.parentProperty].indexOf(sItem) < 0)
			{
				iNS = sItem.indexOf(':');
				if (iNS > -1) {
					oWidget.setAttributeNS(this.xmlNS[sItem.substr(0, iNS)], sItem, oConfig[sItem]);
				}
				else {
					oWidget.setAttribute(sItem, oConfig[sItem]);
				}
			}
		}
		if (oConfig[this.cssProperty]) {
			oWidget.setAttribute('class', oConfig[this.cssProperty]);
		}
		if (oConfig.style) {
			if (bAmple) {
				oWidget.setAttribute('style', oConfig.style);
			}
			else {
				oWidget.style.cssText = oWidget.style.cssText +';'+ oConfig.style;
			}
		}
		if (oConfig[this.htmlProperty]) {
			if (bAmple) {
				ample.query(oWidget).append(oConfig[this.htmlProperty].substr(0, 1) === '<' ?
					oConfig[this.htmlProperty] : '<span>' + oConfig[this.htmlProperty] + '</span>');
			}
			else {
				oWidget.innerHTML = oConfig[this.htmlProperty];
			}
		}
		for (sItem in oConfig) {
			if (oConfig.hasOwnProperty(sItem) && JUL.typeOf(oConfig[sItem]) === 'Array' &&
				[].concat(this.childrenProperty, this.membersProperties).indexOf(sItem) > -1) {
				var aMembers = oConfig[sItem];
				var oMembersWidget =  oWidget;
				if (sItem !== this.childrenProperty) {
					iNS = sItem.indexOf(':');
					if (iNS > -1) { sNS = sItem.substr(0, iNS);	}
					oMembersWidget = sNS === 'html' ? oDocument.createElement(iNS > -1 ? sItem.substr(iNS + 1) : sItem) :
						oDocument.createElementNS(this.xmlNS[sNS], iNS > -1 ? sItem : sNS + ':' + sItem);
				}
				for (var k = 0; k < aMembers.length; k++) {
					oMembersWidget.appendChild(aMembers[k]);
				}
				if (sItem !== this.childrenProperty) {
					oWidget.appendChild(oMembersWidget);
				}
			}
		}
		if (this.topDown && oConfig[this.parentProperty] &&
			typeof oConfig[this.parentProperty] === 'object') {
			oConfig[this.parentProperty].appendChild(oWidget);
		}
		return oWidget;
	},
	
	/**
	 * Converts a XML DOM to a JUL.UI config tree or to JavaScript code
	 * @param	{Object|String}	oXml		DOM XML object or XML string to convert
	 * @param	{Boolean}	[bReturnString]	Whether to serialize converted object to JavaScript code
	 * @returns	{Object|String}	JUL.UI config tree or its JavaScript code
	 */
	xml2jul: function(oXml, bReturnString) {
		if (typeof oXml !== 'object') {
			oXml = this._createXml(oXml);
		}
		if (oXml.error) {
				return bReturnString ? this.obj2str(oXml) : oXml;
		}
		if (oXml.parseError && oXml.parseError.errorCode !== 0) {
			return bReturnString ? this.obj2str({error: oXml.parseError.reason}) : {error: oXml.parseError.reason};
		}
		var oData = {};
		var dom2jul = function(oData, oNode, bNoTag) {
			if (oNode.nodeName === 'parsererror') {
				oData.error = oNode.textContent;
				return;
				}
			var iNS = oNode.nodeName.indexOf(':');
			if (!bNoTag && iNS > -1) {
				if (this.defaultClass !== oNode.nodeName.substr(0, iNS)) {
					oData[this.classProperty] = oNode.nodeName.substr(0, iNS);
				}
				oData[this.tagProperty] = oNode.nodeName.substr(iNS + 1);
			}
			else {
				if (!bNoTag || this.defaultClass !== oNode.nodeName) {
					oData[bNoTag ? this.classProperty : this.tagProperty] = oNode.nodeName;
				}
			}
			for (var i = 0; i < oNode.attributes.length; i++) {
				var oAttribute = oNode.attributes[i];
				if (oAttribute.name.substr(0, 5) !== 'xmlns') { 
					oData[oAttribute.name === 'class' ? 'css' : oAttribute.name] =
						this._regExps.number.test(oAttribute.value) || this._regExps.special.test(oAttribute.value) ?
						JSON.parse(oAttribute.value) : oAttribute.value;
				}
			}
			if (oNode.childNodes.length && oNode.firstChild.nodeType === 3 &&
				JUL.trim(oNode.firstChild.nodeValue)) {
				oData[this.htmlProperty] = oNode.firstChild.nodeValue;
			}
			var aNames = [];
			var oRepeat = {};
			for (i = 0; i < oNode.childNodes.length; i++) {
				var oChild = oNode.childNodes[i];
				if (oChild.nodeType === 1) {
					if (aNames.indexOf(oChild.nodeName) > -1) {
						oRepeat[oChild.nodeName] = true;
					}
					else {
						aNames.push(oChild.nodeName);
					}
				}
			}
			for (i = 0; i < oNode.childNodes.length; i++) {
				oChild = oNode.childNodes[i];
				if (oChild.nodeType === 1) {
					iNS = oChild.nodeName.indexOf(':');
					var sTag = !bNoTag && iNS > -1 && this.defaultClass === oChild.nodeName.substr(0, iNS) ?
						oChild.nodeName.substr(iNS + 1) : oChild.nodeName;
					if (!oRepeat[oChild.nodeName] &&
						[].concat(this.childrenProperty, this.membersProperties).indexOf(sTag) > -1) {
						var aMembers = [];
						oData[sTag] = aMembers;
						for (var k = 0; k < oChild.childNodes.length; k++) {
							var oGrandChild = oChild.childNodes[k];
							if (oGrandChild.nodeType === 1) {
								aMembers.push({});
								dom2jul.call(this, aMembers[aMembers.length - 1], oGrandChild, bNoTag);
							}
						}
					}
					else {
						aMembers = oData[this.childrenProperty] || [];
						oData[this.childrenProperty] = aMembers;
						aMembers.push({});
						dom2jul.call(this, aMembers[aMembers.length - 1], oChild, bNoTag);
					}
				}
			}
		};
		dom2jul.call(this, oData, oXml.documentElement, !this.useTags);
		return bReturnString ? this.obj2str(oData) : oData;
	},

	/**
	 * Recursively converts a config object to JavaScript or to JSON
	 * @param	{Object}	oData	Config object
	 * @param	{Boolean}	[bQuote]	Set it to true to obtain JSON instead of JavaScript
	 * @returns	{String}	JavaScript code or JSON string
	 */
	obj2str: function(oData, bQuote) {
		var sData = JSON.stringify(oData, this._jsonReplacer);
		if (!sData) { return ''; }
		var ca = '';
		var c = '';
		var sIndent = '';
		var sResult = '';
		var bString = false;
		var aStack = [];
		var iStart = 0;
		for (var i = 0; i < sData.length; i++) {
			c = sData.substr(i, 1);
			if (c === '"' && ca !== '\\') {
				bString = !bString;
				if (bString) { iStart = i; }
			}
			if (bString) {
				if (bQuote) { sResult = sResult + c; }
				ca = c;
				continue;
			}
			if (c === '"' && !bQuote) {
				var sItem = JSON.parse(sData.substr(iStart, i - iStart + 1));
				if (sData.substr(i + 1, 1) === ':') {
					sResult = sResult + (!this._regExps.keyword.test(sItem) && (this._regExps.variable.test(sItem) || this._regExps.uint.test(sItem)) ?
						sItem : "'" + sData.substr(iStart + 1, i - iStart - 1).replace(/'/g, "\\'") + "'" );
				}
				else {
					if (typeof sItem === 'string' && this._regExps.functionStart.test(sItem)) {
						var oPrefix = sItem.match(/\n(\s|\t)+\}$/);
						if (oPrefix) {
							oPrefix = oPrefix[0].substr(0, oPrefix[0].length - 1);
							oPrefix = new RegExp(oPrefix.replace(/\\/g, '\\\\'), 'g');
							sItem = sItem.replace(oPrefix, '\n');
						}
						sItem = sItem.replace(/\t/g, this._tabString)
							.replace(/\n/g, this._newlineString + sIndent);
						sResult = sResult + sItem;
						bString = false;
						ca = '~';
						continue;
					}
					else {
						sResult = sResult + (typeof sItem !== 'string' || this._regExps.regexp.test(sItem) ?
							sItem.toString() : "'" + sData.substr(iStart + 1, i - iStart - 1).replace(/'/g, "\\'") + "'" );
					}
				}
			}
			else if (c === ':' && !bQuote) {
				sResult = sResult + c + this._spaceString;
			}
			else if (c === '{' || c === '[') {
				aStack.push(c);
				if ((aStack.length === 1 && sData.substr(i + 1, 1) !== '}' && sData.substr(i + 1, 1) !== ']') ||
					(c === '{' && ca === ':' && sData.substr(i + 1, 1) !== '}') ||
					(c === '[' && (sData.substr(i + 1, 1) === '{' || sData.substr(i + 1, 1) === '['))) {
					sIndent = sIndent + this._tabString;
					sResult = sResult + c + this._newlineString + sIndent;
				}
				else {
					sResult = sResult + c;
				}
			}
			else if (c === ',') {
				if (aStack.length === 1 || ca === '~' || ((ca === '}' || ca === ']') &&
					sData.substr(i - 2, 1) !== '{' && sData.substr(i - 2, 1) !== '[')) {
					sResult = sResult + c + this._newlineString + sIndent;
				}
				else if(!bQuote) {
					sResult = sResult + c + this._spaceString;
				}
				else {
					sResult = sResult + c;
				}
			}
			else if (c === '}' || c === ']') {
				aStack.pop();
				if ((!aStack.length && ca !== '{' && ca !== '[') ||
					(c === '}' && aStack.length && aStack[aStack.length - 1] === '{' && ca !== '{') ||
					(c === ']' && (ca === '}'  || ca === ']'))) {
					sIndent = sIndent.substr(this._tabString.length);
					sResult = sResult + this._newlineString + sIndent + c;
				}
				else {
					sResult = sResult + c;
				}
			}
			else {
				sResult = sResult + c;
			}
			ca = c;
		}
		return sResult;
	},
	
	/**
	 * Expands a compacted config tree converting all 'membersProperties' properties into 'childrenProperty' items
	 * @param	{Object}	oData	Compacted config tree
	 * @returns	{Object}	Expanded config tree
	 */
	expand: function(oData) {
		oData = this.include(oData);
		var aChildren = [];
		for (var sItem in oData) {
			if (oData.hasOwnProperty(sItem) && oData[sItem] && typeof oData[sItem] === 'object' &&
				[].concat(this.childrenProperty, this.membersProperties).indexOf(sItem) > -1) {
				if (sItem === this.childrenProperty) {
					aChildren = aChildren.concat(oData[sItem]);
				}
				else {
					var oNew = {};
					if (!this.useTags && sItem !== this.defaultClass) { oNew[this.classProperty] = sItem; }
					if (this.useTags) {
						var iNS = sItem.indexOf(':');
						if (iNS > -1 && sItem.substr(0, iNS) !== this.defaultClass) { oNew[this.classProperty] = sItem.substr(0, iNS); }
						oNew[this.tagProperty] = iNS > -1 ? sItem.substr(iNS + 1) : sItem;
					}
					oNew[this.childrenProperty] = [].concat(oData[sItem]);
					delete oData[sItem];
					aChildren.push(oNew);
				}
			}
		}
		if (aChildren.length) {
			oData[this.childrenProperty] = aChildren;
			for (var i = 0; i < aChildren.length; i++) {
				aChildren[i] = this.expand(aChildren[i]);
			}
		}
		return oData;
	},
	
	/**
	 * Compacts a config tree converting suitable 'childrenProperty' items into 'membersProperties' properties
	 * @param	{Object}	oData	Config tree
	 * @param	{Boolean}	[bAuto]	Autodetect compactable items and add their names to the 'membersProperties' array of the parser
	 * @returns	{Object}	Compacted config tree
	 */
	compact: function(oData, bAuto, _iLength) {
		oData = this.include(oData);
		if (JUL.typeOf(oData[this.childrenProperty]) !== 'Array') { return oData; }
		var aItems = [];
		var oRepeat = {};
		for (var i = 0; i < oData[this.childrenProperty].length; i++) {
			var oChild = oData[this.childrenProperty][i];
			var sClass = oChild[this.classProperty] || this.defaultClass;
			var sName = this.useTags ? sClass + ':' + oChild[this.tagProperty] : sClass;
			if (aItems.indexOf(sName) < 0) {
				aItems.push(sName);
			}
			else {
				oRepeat[sName] = true;
			}
		}
		var oNew = {};
		for (var sItem in oData) {
			if (oData.hasOwnProperty(sItem) && sItem !== this.childrenProperty) {
				oNew[sItem] = oData[sItem];
			}
		}
		var oAdd = {};
		if (_iLength === undefined) { _iLength = this.membersProperties.length; }
		for (i = 0; i < oData[this.childrenProperty].length; i++) {
			oChild = oData[this.childrenProperty][i];
			sClass = oChild[this.classProperty] || this.defaultClass;
			sName = this.useTags ? sClass + ':' + oChild[this.tagProperty] : sClass;
			var sTag = this.useTags && sClass === this.defaultClass ? oChild[this.tagProperty] : sName;
			var bCompact = typeof oChild[this.childrenProperty] === 'object' && oChild[this.childrenProperty].length;
			var iPos = this.membersProperties.indexOf(sTag);
			if (bCompact && (iPos < 0 || iPos >= _iLength)) {
				if (bAuto) {
					for (sItem in oChild) {
						if (oChild.hasOwnProperty(sItem) && sItem !== this.classProperty &&
							sItem !== this.childrenProperty && !(this.useTags && sItem === this.tagProperty)) {
							bCompact = false;
							break;
						}
					}
				}
				else {
					bCompact = false;
				}
			}
			if (bCompact && !oRepeat[sName]) {
				oAdd[sTag] = oChild[this.childrenProperty];
			}
			else {
				oAdd[this.childrenProperty] = oAdd[this.childrenProperty] || [];
				oAdd[this.childrenProperty].push(oChild);
			}
		}
		for (sItem in oAdd) {
			if (oAdd.hasOwnProperty(sItem)) {
				oNew[sItem] = oAdd[sItem];
				if (sItem !== this.childrenProperty && this.membersProperties.indexOf(sItem) < 0) {
					this.membersProperties.push(sItem);
				}
				for (i = 0; i < oAdd[sItem].length; i++) {
					oAdd[sItem][i] = this.compact(oAdd[sItem][i], bAuto, _iLength);
				}
			}
		}
		return oNew;
	},
	
	/**
	 * Creates a new JUL.UI parser that inherits all JUL.UI members
	 * @class
	 * Used to build parser objects for config trees	 
	 *	 	 
	 * @constructor
	 * @extends	JUL.UI	 
	 * @param	{Object}	oConfig	Parser config object to override inherited members
	 * @returns	{Object}	JUL.UI parser instance
	 */
	Parser: function(oConfig) {
			this.Parser.prototype = this;
		JUL.apply(this, oConfig);
	},
		
	/**
	 * Tab delimiter for serialized strings
	 * @type	String
	 * @private
	 */
	_tabString: '\t',

	/**
	 * New line delimiter for serialized strings
	 * @type	String
	 * @private
	 */
	_newlineString: '\n',
	
	/**
	 * Space delimiter for serialized strings
	 * @type	String
	 * @private
	 */
	_spaceString: ' ',
	
	/**
	 * Several RegExp patterns used across JUL.UI
	 * @type	Object
	 * @private
	 */
	_regExps: {
		variable: /^[a-z$_][\w$]*$/i,
		number: /^[\-+]?[0-9]*\.?[0-9]+([eE][\-+]?[0-9]+)?$/,
		uint: /^\d+$/,
		functionStart: /^function\s*\(/,
		regexp: /^\/(\s|\S)+\/[gim]{0,3}$/,
		special: /^(true|false|null)$/,
		keyword: new RegExp('^(' +
			['break', 'case', 'catch', 'continue', 'debugger', 'default', 'delete', 'do',
			'else', '|finally', 'for', 'function', 'if', 'in', 'instanceof', 'new', 'return',
			'switch', 'this', 'throw', 'try', 'typeof', 'var', 'void', 'while', 'with',
			'class', 'enum', 'export', 'extends', 'import', 'super', 'implements', 'interface',
			'let', 'package', 'private', 'protected', 'public', 'static', 'yield'].join('|') +
		')$')
	},
	
	/**
	 * Used for debugging purposes
	 * @type	Boolean
	 * @private
	 */
	_keepInstance: false,
	
	/**
	 * Used for debugging purposes
	 * @type	String
	 * @private
	 */
	_instanceProperty: '_instance',
	
	/**
	 * Callback used internally by the serializer
	 * @private	 
	 */
	_jsonReplacer: function(sKey, oValue) {
		if (sKey && (typeof oValue === 'function' || JUL.typeOf(oValue) === 'RegExp' )) {
			return oValue.toString();
		}
		return oValue;
	},

	/**
	 * Utility wrapper of browser's XML parser
	 * @private	 
	 */
	_createXml: function(sXml) {
		if (window.DOMParser) {
			this._xmlParser = this._xmlParser || new DOMParser();
			try {
				return this._xmlParser.parseFromString(sXml, 'application/xhtml+xml');
			}
			catch(e) {
				return {error: e.message};
			}
		}
		else {
			this._xmlParser = this._xmlParser || new ActiveXObject('Msxml2.DOMDocument.3.0');
			 this._xmlParser.async = false;
			 this._xmlParser.loadXML(sXml);
			return  this._xmlParser; 
		}
	}
};

// Making the JUL.UI members available to the parser
JUL.UI.Parser.prototype = JUL.UI;
